/*
 * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.xuexiang.xdlink.util;

import android.support.annotation.NonNull;

import java.util.ArrayList;
import java.util.List;

/**
 * 转换相关工具类
 * (【小端】低位在前，高位在后)
 * (【大端】高位在前，低位在后) 符合我们正常的阅读习惯，在默认情况下，一般都是大端存储。
 *
 * @author xuexiang
 * @since 2018/8/16 上午1:06
 */
public final class ConvertTools {

    private ConvertTools() {
        throw new UnsupportedOperationException("u can't instantiate me...");
    }

    private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

    public static String byte2HexString(byte byte1) {
        return bytes2HexString(new byte[]{byte1});
    }

    /**
     * byteArr转hexString
     * <p>
     * 例如：
     * </p>
     * bytes2HexString(new byte[] { 0, (byte) 0xa8 }) returns 00A8
     *
     * @param bytes 字节数组
     * @return 16进制大写字符串
     */
    public static String bytes2HexString(byte[] bytes) {
        if (bytes == null) {
            return null;
        }
        int len = bytes.length;
        if (len <= 0) {
            return null;
        }
        char[] ret = new char[len << 1];
        for (int i = 0, j = 0; i < len; i++) {
            ret[j++] = HEX_DIGITS[bytes[i] >>> 4 & 0x0f];
            ret[j++] = HEX_DIGITS[bytes[i] & 0x0f];
        }
        return new String(ret);
    }

    /***
     * byte[] 转16进制字符串
     * @param src
     * @return
     */
    public static String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }

    /**
     * hexString转byteArr
     *
     * @param hexString
     * @return
     */
    public static byte[] hexStringToBytes(String hexString) {
        if (hexString == null || "".equals(hexString)) {
            return null;
        }
        hexString = hexString.toUpperCase();
        int length = hexString.length() / 2;
        char[] hexChars = hexString.toCharArray();
        byte[] d = new byte[length];
        for (int i = 0; i < length; i++) {
            int pos = i * 2;
            d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
        }
        return d;
    }

    public static byte charToByte(char c) {
        return (byte) "0123456789ABCDEF".indexOf(c);
    }

    /**
     * 分包
     *
     * @param data        要分包的数据
     * @param packageSize 每个包的长度
     * @return
     */
    public static List<byte[]> subPackage(@NonNull byte[] data, int packageSize) {
        List<byte[]> dataList = new ArrayList<>();
        int index = 0;
        for (int i = 0; i < data.length / packageSize; i++) {
            byte[] temp = new byte[packageSize];
            System.arraycopy(data, index, temp, 0, temp.length);
            index += packageSize;
            dataList.add(temp);
        }
        int lastLength = data.length % packageSize;
        if (lastLength != 0) {
            byte[] temp = new byte[lastLength];
            System.arraycopy(data, index, temp, 0, temp.length);
            dataList.add(temp);
        }
        return dataList;
    }

    // =========================一位byte和int的转换==================================//

    /**
     * 一位byte转int【无符号】
     *
     * @param b
     * @return 【0 ~ 255】
     */
    public static int byteToIntUnSigned(byte b) {
        return b & 0xFF;
    }

    /**
     * 一位byte转int【有符号】
     *
     * @param b
     * @return 【-128 ~ 127】
     */
    public static int byteToIntSigned(byte b) {
        return b;
    }

    /**
     * int转Byte 【仅对0~255的整型有效】
     *
     * @param value 【0~255】
     * @return
     */
    public static byte intToByte(int value) {
        return (byte) value;
    }

    //========================【object <---> byte】=============================================//

    /**
     * 获取object转化为bytes数组所需要的长度
     *
     * @param value
     * @return
     */
    public static int getObject2BytesLength(@NonNull Object value) {
        int length = 0;
        if (value instanceof Integer) {
            length = 4;
        } else if (value instanceof Short) {
            length = 2;
        } else if (value instanceof Byte) {
            length = 1;
        } else if (value instanceof int[]) {
            length = ((int[]) value).length * 4;
        } else if (value instanceof short[]) {
            length = ((short[]) value).length * 2;
        } else if (value instanceof byte[]) {
            length = ((byte[]) value).length;
        } else if (value instanceof String) {
            length = ((String) value).getBytes().length;
        }
        return length;
    }

    /**
     * 将Object对象【基础类型】填充至byte数组的指定位置，本方法适用于(【小端】低位在前，高位在后)的顺序。
     *
     * @param value  object对象【int、short、byte、int[]、short、byte[]、String】
     * @param src
     * @param offset
     */
    public static int fillObjectToBytesLittleEndian(@NonNull Object value, @NonNull byte[] src, int offset) {
        if (value instanceof Integer) {
            offset += fillIntToBytesLittleEndian((Integer) value, src, offset);
        } else if (value instanceof Short) {
            offset += fillShortToBytesLittleEndian((Short) value, src, offset);
        } else if (value instanceof Byte) {
            src[offset] = (byte) value;
            offset += 1;
        } else if (value instanceof int[]) {
            for (int i : (int[]) value) {
                fillIntToBytesLittleEndian(i, src, offset);
                offset += 4;
            }
        } else if (value instanceof short[]) {
            for (short s : (short[]) value) {
                fillShortToBytesLittleEndian(s, src, offset);
                offset += 2;
            }
        } else if (value instanceof byte[]) {
            byte[] bytes = (byte[]) value;
            System.arraycopy(bytes, 0, src, offset, bytes.length);
            offset += bytes.length;
        } else if (value instanceof String) {
            byte[] bytes = ((String) value).getBytes();
            System.arraycopy(bytes, 0, src, offset, bytes.length);
            offset += bytes.length;
        }
        return offset;
    }

    /**
     * 将Object对象【基础类型】填充至byte数组的指定位置，本方法适用于(【大端】高位在前，低位在后)的顺序。
     *
     * @param value  object对象【int、short、byte、int[]、short、byte[]、String】
     * @param src
     * @param offset
     */
    public static int fillObjectToBytesBigEndian(@NonNull Object value, @NonNull byte[] src, int offset) {
        if (value instanceof Integer) {
            offset += fillIntToBytesBigEndian((Integer) value, src, offset);
        } else if (value instanceof Short) {
            offset += fillShortToBytesBigEndian((Short) value, src, offset);
        } else if (value instanceof Byte) {
            src[offset] = (byte) value;
            offset += 1;
        } else if (value instanceof int[]) {
            for (int i : (int[]) value) {
                fillIntToBytesBigEndian(i, src, offset);
                offset += 4;
            }
        } else if (value instanceof short[]) {
            for (short s : (short[]) value) {
                fillShortToBytesBigEndian(s, src, offset);
                offset += 2;
            }
        } else if (value instanceof byte[]) {
            byte[] bytes = (byte[]) value;
            System.arraycopy(bytes, 0, src, offset, bytes.length);
            offset += bytes.length;
        } else if (value instanceof String) {
            byte[] bytes = ((String) value).getBytes();
            System.arraycopy(bytes, 0, src, offset, bytes.length);
            offset += bytes.length;
        }
        return offset;
    }


    /**
     * byte数组中解析出object【int、short、byte】，本方法适用于(【小端】低位在前，高位在后)的顺序
     *
     * @param src
     * @param offset
     */
    public static <T> T bytesToObjectLittleEndian(@NonNull byte[] src, int offset, @NonNull Class<T> clazz) {
        Object value = null;
        if (clazz.equals(int.class)) {
            value = bytesToIntLittleEndian(src, offset);
        } else if (clazz.equals(short.class)) {
            value = bytesToShortLittleEndian(src, offset);
        } else if (clazz.equals(byte.class)) {
            value = src[offset];
        }
        return (T) value;
    }

    /**
     * byte数组中解析出object【int、short、byte】，本方法适用于(【大端】高位在前，低位在后)的顺序
     *
     * @param src
     * @param offset
     */
    public static <T> T bytesToObjectBigEndian(@NonNull byte[] src, int offset, @NonNull Class<T> clazz) {
        Object value = null;
        if (clazz.equals(int.class)) {
            value = bytesToIntBigEndian(src, offset);
        } else if (clazz.equals(short.class)) {
            value = bytesToShortBigEndian(src, offset);
        } else if (clazz.equals(byte.class)) {
            value = src[offset];
        }
        return (T) value;
    }

    /**
     * byte数组中解析出object数组【int、short、byte】，本方法适用于(【大端】高位在前，低位在后)的顺序
     *
     * @param src
     * @param offset
     */
    public static <T> T bytesToObjectArrayBigEndian(@NonNull byte[] src, int offset, int length, @NonNull Class<T> clazz) {
        Object value = null;
        if (clazz.equals(int[].class)) {
            int[] array = new int[length];
            for (int i = 0; i < length; i++) {
                array[i] = bytesToIntBigEndian(src, offset);
                offset += 4;
            }
            value = array;
        } else if (clazz.equals(short[].class)) {
            short[] array = new short[length];
            for (int i = 0; i < length; i++) {
                array[i] = bytesToShortBigEndian(src, offset);
                offset += 2;
            }
            value = array;
        } else if (clazz.equals(byte[].class) || clazz.equals(String.class)) {
            byte[] bytes = new byte[length];
            System.arraycopy(src, offset, bytes, 0, length);
            if (clazz.equals(byte[].class)) {
                value = bytes;
            } else {
                value = new String(bytes);
            }
        }
        return (T) value;
    }

    /**
     * byte数组中解析出object数组【int、short、byte】，本方法适用于(【小端】低位在前，高位在后)的顺序
     *
     * @param src
     * @param offset
     */
    public static <T> T bytesToObjectArrayLittleEndian(@NonNull byte[] src, int offset, int length, @NonNull Class<T> clazz) {
        Object value = null;
        if (clazz.equals(int[].class)) {
            int[] array = new int[length];
            for (int i = 0; i < length; i++) {
                array[i] = bytesToIntLittleEndian(src, offset);
                offset += 4;
            }
            value = array;
        } else if (clazz.equals(short[].class)) {
            short[] array = new short[length];
            for (int i = 0; i < length; i++) {
                array[i] = bytesToShortLittleEndian(src, offset);
                offset += 2;
            }
            value = array;
        } else if (clazz.equals(byte[].class) || clazz.equals(String.class)) {
            byte[] bytes = new byte[length];
            System.arraycopy(src, offset, bytes, 0, length);
            if (clazz.equals(byte[].class)) {
                value = bytes;
            } else {
                value = new String(bytes);
            }
        }
        return (T) value;
    }


    // ======================【byte数组<-->（无符号）int】=====================================//

    /**
     * 将int数值转换为占四个字节的byte数组，本方法适用于(【小端】低位在前，高位在后)的顺序。 和
     * {@link #bytesToIntLittleEndian(byte[], int)}配套使用
     *
     * @param value 要转换的int值
     * @return byte数组
     */
    public static byte[] intToBytesLittleEndian(int value) {
        byte[] src = new byte[4];
        src[0] = (byte) (value & 0xFF);
        src[1] = (byte) ((value >> 8) & 0xFF);
        src[2] = (byte) ((value >> 16) & 0xFF);
        src[3] = (byte) ((value >> 24) & 0xFF);
        return src;
    }

    /**
     * 将int数值填充至byte数组的指定位置，本方法适用于(【小端】低位在前，高位在后)的顺序
     *
     * @param value  填充的int值
     * @param src    需要填充的byte数组
     * @param offset 填充的位置
     */
    public static int fillIntToBytesLittleEndian(int value, @NonNull byte[] src, int offset) {
        src[offset] = (byte) (value & 0xFF);
        src[offset + 1] = (byte) ((value >> 8) & 0xFF);
        src[offset + 2] = (byte) ((value >> 16) & 0xFF);
        src[offset + 3] = (byte) ((value >> 24) & 0xFF);
        return offset + 4;
    }

    /**
     * 将int数值转换为占四个字节的byte数组，本方法适用于(【大端】高位在前，低位在后)的顺序。 和
     * {@link #bytesToIntBigEndian(byte[], int)}配套使用
     *
     * @param value 要转换的int值
     * @return byte数组
     */
    public static byte[] intToBytesBigEndian(int value) {
        byte[] src = new byte[4];
        src[0] = (byte) ((value >> 24) & 0xFF);
        src[1] = (byte) ((value >> 16) & 0xFF);
        src[2] = (byte) ((value >> 8) & 0xFF);
        src[3] = (byte) (value & 0xFF);
        return src;
    }

    /**
     * 将int数值填充至byte数组的指定位置，本方法适用于(【大端】高位在前，低位在后)的顺序
     *
     * @param value  填充的int值
     * @param src    需要填充的byte数组
     * @param offset 填充的位置
     */
    public static int fillIntToBytesBigEndian(int value, @NonNull byte[] src, int offset) {
        src[offset] = (byte) ((value >> 24) & 0xFF);
        src[offset + 1] = (byte) ((value >> 16) & 0xFF);
        src[offset + 2] = (byte) ((value >> 8) & 0xFF);
        src[offset + 3] = (byte) (value & 0xFF);
        return offset + 4;
    }

    /**
     * byte数组中取int数值，本方法适用于(【小端】低位在前，高位在后)的顺序，和
     * {@link #intToBytesLittleEndian(int)}配套使用
     *
     * @param src    byte数组
     * @param offset 从数组的第offset位开始
     * @return int数值
     */
    public static int bytesToIntLittleEndian(@NonNull byte[] src, int offset) {
        int value;
        value = (src[offset] & 0xFF) | ((src[offset + 1] & 0xFF) << 8) | ((src[offset + 2] & 0xFF) << 16) | ((src[offset + 3] & 0xFF) << 24);
        return value;
    }

    /**
     * byte数组中取int数值，本方法适用于(【大端】高位在前，低位在后)的顺序。和{@link #intToBytesBigEndian(int)}
     * 配套使用
     */
    public static int bytesToIntBigEndian(@NonNull byte[] src, int offset) {
        int value;
        value = ((src[offset] & 0xFF) << 24) | ((src[offset + 1] & 0xFF) << 16) | ((src[offset + 2] & 0xFF) << 8) | (src[offset + 3] & 0xFF);
        return value;
    }

    // ======================【byte数组<-->（无符号）short】=====================================//

    /**
     * 将short数值转换为占两个字节的byte数组，本方法适用于(【小端】低位在前，高位在后)的顺序。 和
     * {@link #bytesToShortLittleEndian(byte[], int)}配套使用
     *
     * @param value 要转换的short值
     * @return byte数组
     */
    public static byte[] shortToBytesLittleEndian(short value) {
        byte[] src = new byte[2];
        src[0] = (byte) (value & 0xFF);
        src[1] = (byte) ((value >> 8) & 0xFF);
        return src;
    }

    /**
     * 将short数值填充至byte数组的指定位置，本方法适用于(【小端】低位在前，高位在后)的顺序
     *
     * @param value  填充的short值
     * @param src    需要填充的byte数组
     * @param offset 填充的位置
     */
    public static int fillShortToBytesLittleEndian(short value, @NonNull byte[] src, int offset) {
        src[offset] = (byte) (value & 0xFF);
        src[offset + 1] = (byte) ((value >> 8) & 0xFF);
        return offset + 2;
    }

    /**
     * 将无符号short数值填充至byte数组的指定位置，本方法适用于(【小端】低位在前，高位在后)的顺序
     * 【因为是无符号short数值，java中short默认是有符号的，不能满足无符号short数值的表示范围，因此使用int数值来表示】
     *
     * @param value  填充的无符号short数值
     * @param src    需要填充的byte数组
     * @param offset 填充的位置
     */
    public static int fillUnsignedShortToBytesLittleEndian(int value, @NonNull byte[] src, int offset) {
        src[offset] = (byte) (value & 0xFF);
        src[offset + 1] = (byte) ((value >> 8) & 0xFF);
        return offset + 2;
    }

    /**
     * 将short数值转换为占两个字节的byte数组，本方法适用于(【大端】高位在前，低位在后)的顺序。 和
     * {@link #bytesToShortBigEndian(byte[], int)}配套使用
     *
     * @param value 要转换的short值
     * @return byte数组
     */
    public static byte[] shortToBytesBigEndian(short value) {
        byte[] src = new byte[2];
        src[0] = (byte) ((value >> 8) & 0xFF);
        src[1] = (byte) (value & 0xFF);
        return src;
    }

    /**
     * 将short数值填充至byte数组的指定位置，本方法适用于(【大端】高位在前，低位在后)的顺序
     *
     * @param value  填充的short值
     * @param src    需要填充的byte数组
     * @param offset 填充的位置
     */
    public static int fillShortToBytesBigEndian(short value, @NonNull byte[] src, int offset) {
        src[offset] = (byte) ((value >> 8) & 0xFF);
        src[offset + 1] = (byte) (value & 0xFF);
        return offset + 2;
    }

    /**
     * 将无符号short数值填充至byte数组的指定位置，本方法适用于(【大端】高位在前，低位在后)的顺序
     * 【因为是无符号short数值，java中short默认是有符号的，不能满足无符号short数值的表示范围，因此使用int数值来表示】
     *
     * @param value  填充的无符号short数值
     * @param src    需要填充的byte数组
     * @param offset 填充的位置
     */
    public static int fillUnsignedShortToBytesBigEndian(int value, @NonNull byte[] src, int offset) {
        src[offset] = (byte) ((value >> 8) & 0xFF);
        src[offset + 1] = (byte) (value & 0xFF);
        return offset + 2;
    }

    /**
     * byte数组中取short数值，本方法适用于(【小端】低位在前，高位在后)的顺序，和
     * {@link #shortToBytesLittleEndian(short)}配套使用
     *
     * @param src    byte数组
     * @param offset 从数组的第offset位开始
     * @return short数值
     */
    public static short bytesToShortLittleEndian(@NonNull byte[] src, int offset) {
        short value;
        value = (short) ((src[offset] & 0xFF) | ((src[offset + 1] & 0xFF) << 8));
        return value;
    }

    /**
     * byte数组中取short数值，本方法适用于(【大端】高位在前，低位在后)的顺序。和
     * {@link #shortToBytesBigEndian(short)}配套使用
     *
     * @param src    byte数组
     * @param offset 从数组的第offset位开始
     * @return short数值
     */
    public static short bytesToShortBigEndian(@NonNull byte[] src, int offset) {
        short value;
        value = (short) (((src[offset] & 0xFF) << 8) | (src[offset + 1] & 0xFF));
        return value;
    }
}
